#Region "Imports"
Imports System.Collections.Generic
#End Region
''' <summary>
''' This is the class for a pdf object
''' </summary>
''' <remarks></remarks>
Friend Class clsPdfBaseObject
    Implements IComparable(Of clsPdfBaseObject)
#Region "Private members"
    Private _type As PdfObjectTypes         'The object's type
    Private _subType As PdfObjectSubTypes   'Some object's type have sub types
    Private _childs As New List(Of clsPdfBaseObject) 'A PDF object can contains more pdf objects
    Private _childsParsed As Boolean        'Indicate if we have parsed the object contents
    Private _value() As Byte                'The object's contents in binary
    Private _count As Integer               'The number of bytes in the object
    Private _objectNumber As Long           'Its number
    Private _generationNumber As Long       'Its generation numner
    Private _position As Long               'The absolute position of the object in the PDF file
#End Region
#Region "Constructors"
    ''' <summary>
    ''' Create a new pdf object
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        'We assigned unknown types to the object
        _type = PdfObjectTypes.UnknownObject
        _subType = PdfObjectSubTypes.NoSubType
        _childsParsed = False
        _position = -1
    End Sub
    Public Sub New(ByVal type As PdfObjectTypes, ByVal subtype As PdfObjectSubTypes)
        _type = type
        _subType = subtype
        _childsParsed = False
        _position = -1
    End Sub
#End Region
#Region "Public Properties"
    ''' <summary>
    ''' Get the number of bytes in the object's content
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Count() As Long
        Get
            Count = _count
        End Get
    End Property
    Public Property Position() As Long
        Get
            Position = _position
        End Get
        Set(ByVal value As Long)
            _position = value
        End Set
    End Property
    ''' <summary>
    ''' Set the value of the object
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Value() As Byte()
        Get
            Value = _value
        End Get
        Set(ByVal value As Byte())
            _value = value
        End Set
    End Property
    ''' <summary>
    ''' Number of child's objects
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ChildsCount() As Integer
        Get
            ChildsCount = _childs.Count
        End Get
    End Property
    ''' <summary>
    ''' The PDF object number
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ObjectNumber() As Long
        Get
            ObjectNumber = _objectNumber
        End Get
        Set(ByVal value As Long)
            _objectNumber = value
        End Set
    End Property
    ''' <summary>
    ''' The PDF object generation number
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property GenerationNumber() As Long
        Get
            GenerationNumber = _generationNumber
        End Get
        Set(ByVal value As Long)
            _generationNumber = value
        End Set
    End Property
    ''' <summary>
    ''' The PDF object sub type
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property SubType() As PdfObjectSubTypes
        Get
            SubType = _subType
        End Get
        Set(ByVal value As PdfObjectSubTypes)
            _subType = value
        End Set
    End Property
    ''' <summary>
    ''' The PDF object type
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Type() As PdfObjectTypes
        Get
            Type = _type
        End Get
        Set(ByVal value As PdfObjectTypes)
            _type = value
        End Set
    End Property
    ''' <summary>
    ''' Set the object's value to string
    ''' </summary>
    ''' <value></value>
    ''' <remarks></remarks>
    Public WriteOnly Property SetString() As String
        Set(ByVal value As String)
            Select Case Type
                Case PdfObjectTypes.StringObject
                    'A string object is ... a string object!
                    If _subType = PdfObjectSubTypes.LiteralStringObject Then
                        _literalStringValue(value)
                    Else
                        If _subType = PdfObjectSubTypes.HexadecimalStringObject Then
                        End If
                    End If
                Case PdfObjectTypes.NameObject
                    'A name object is a string object
                    _literalStringValue(value)
            End Select
        End Set
    End Property
    ''' <summary>
    ''' Get the string stored in the object's content 
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Valid only for string PDF objects</remarks>
    Public ReadOnly Property GetString() As String
        Get
            GetString = ""
            Select Case Type
                Case PdfObjectTypes.CommentObject
                    'A comment object is a string pdf object
                    GetString = _stringValue()
                Case PdfObjectTypes.NameObject
                    'A name object is a string pdf object
                    GetString = _stringValue()
                Case PdfObjectTypes.StringObject
                    'A string object is ... a string object!
                    If _subType = PdfObjectSubTypes.LiteralStringObject Then
                        GetString = _literalStringValue()
                    Else
                        If _subType = PdfObjectSubTypes.HexadecimalStringObject Then
                            GetString = _hexadecimalStringValue()
                        End If
                    End If
                Case Else
                    GetString = ""
            End Select
        End Get
    End Property
    ''' <summary>
    ''' Get a date stored in the object's content 
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>A date is a sring object, it can follows the PDF standard for a date or be written
    ''' in an human readable form </remarks>
    Public ReadOnly Property GetDate() As String
        Get
            GetDate = PdfDateToString(GetString)
        End Get
    End Property
    Public ReadOnly Property GetRectangle() As PdfRectangle
        Get
            GetRectangle = Nothing
            Try
                If Type = PdfObjectTypes.ArrayObject Then
                    'A pdf rectangle object is an array of four numbers
                    Dim a As List(Of clsPdfBaseObject)
                    a = GetArray
                    If a.Count = 4 Then
                        GetRectangle = New PdfRectangle
                        GetRectangle.llx = a.Item(0).GetDouble
                        GetRectangle.lly = a.Item(1).GetDouble
                        GetRectangle.urx = a.Item(2).GetDouble
                        GetRectangle.ury = a.Item(3).GetDouble
                    End If
                End If
            Catch ex As Exception
                GetRectangle = Nothing
            End Try
        End Get
    End Property
    Public ReadOnly Property GetArray() As List(Of clsPdfBaseObject)
        Get
            GetArray = Nothing
            If Type = PdfObjectTypes.ArrayObject And Not _value Is Nothing Then
                Dim obj As clsPdfBaseObject
                GetArray = New List(Of clsPdfBaseObject)
                Dim reader As New clsPDFBaseReader(_value)
                While Not reader.Eof
                    obj = reader.GetObject
                    If obj Is Nothing Then
                        Exit While
                    End If
                    GetArray.Add(obj)
                End While
            End If
        End Get
    End Property
    Public ReadOnly Property GetDouble() As Double
        Get
            If Type = PdfObjectTypes.NumericObject Then
                GetDouble = _doubleValue()
            End If
        End Get
    End Property
    Public ReadOnly Property GetBoolean() As Boolean
        Get
            GetBoolean = False
            If Type = PdfObjectTypes.BooleanObject Then
                If SubType = PdfObjectSubTypes.FalseObject Then
                    GetBoolean = False
                Else
                    If SubType = PdfObjectSubTypes.TrueObject Then
                        GetBoolean = True
                    End If
                End If
            End If
        End Get
    End Property
    Public ReadOnly Property GetLong() As Integer
        Get
            If Type = PdfObjectTypes.NumericObject Or Type = PdfObjectTypes.IndirectReferenceObject Then
                GetLong = _longValue()
            End If
        End Get
    End Property
    Public ReadOnly Property GetPdfDictionnary() As clsPdfDictionnary
        Get
            Dim dictionnary As New clsPdfDictionnary
            GetPdfDictionnary = dictionnary

            Dim key As String
            Dim obj As clsPdfBaseObject
            If Type = PdfObjectTypes.DictionaryObject And Not _value Is Nothing Then
                Dim reader As New clsPDFBaseReader(_value)

                While Not reader.Eof
                    obj = reader.GetObject
                    If obj Is Nothing Then
                        Exit While
                    End If
                    If obj.Type <> PdfObjectTypes.NameObject Then
                        Exit Property
                    End If
                    key = obj.GetString

                    obj = reader.GetObject
                    If obj.Type = PdfObjectTypes.UnknownObject Then
                        Exit Property
                    End If

                    dictionnary.Add(key, obj)
                End While

            End If
        End Get
    End Property
#End Region
#Region "Public Methods"
    ''' <summary>
    ''' For sorting of pdf objects by object number
    ''' </summary>
    ''' <param name="other"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CompareTo(ByVal other As clsPdfBaseObject) As Integer Implements System.IComparable(Of clsPdfBaseObject).CompareTo
        If Me.ObjectNumber = other.ObjectNumber Then
            CompareTo = 0
        Else
            If Me.ObjectNumber > other.ObjectNumber Then
                CompareTo = 1
            Else
                CompareTo = -1
            End If
        End If
    End Function
    ''' <summary>
    ''' Return a child pdf object base on its index
    ''' </summary>
    ''' <param name="index"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Child(ByVal index As Integer) As clsPdfBaseObject
        Child = _childs(index)
    End Function
    Public Function child() As List(Of clsPdfBaseObject)
        child = _childs
    End Function
    ''' <summary>
    ''' Empty an object content
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ClearValue()
        _value = Nothing
    End Sub
    ''' <summary>
    ''' Add a dictionnary to the object's content
    ''' </summary>
    ''' <param name="dictionnary"></param>
    ''' <remarks></remarks>
    Public Sub AddValue(ByVal dictionnary As clsPdfDictionnary)
        Dim key As String
        Dim obj As clsPdfBaseObject
        For Each key In dictionnary.Hashtable.Keys
            AddValue("/" + key + " ")
            obj = dictionnary.Item(key)
            AddValue(obj)
        Next

    End Sub
    ''' <summary>
    ''' Add a base object to the object's content
    ''' </summary>
    ''' <param name="obj"></param>
    ''' <remarks></remarks>
    Public Sub AddValue(ByVal obj As clsPdfBaseObject)
        Dim b As Byte
        If Not obj.Value Is Nothing Then

            If obj.Type = PdfObjectTypes.DictionaryObject Then
                AddValue("<<")
            Else
                If obj.Type = PdfObjectTypes.NameObject Then
                    AddValue("/"c)
                Else
                    If obj.Type = PdfObjectTypes.ArrayObject Then
                        AddValue("["c)
                    End If
                End If
            End If

            For Each b In obj.Value
                AddValue(b)
            Next

            If obj.Type = PdfObjectTypes.IndirectReferenceObject Then
                AddValue(" 0 R")
            Else
                If obj.Type = PdfObjectTypes.DictionaryObject Then
                    AddValue(">>")
                Else
                    If obj.Type = PdfObjectTypes.ArrayObject Then
                        AddValue("]"c)
                    End If
                End If
            End If
        Else
            If obj.Type = PdfObjectTypes.DictionaryObject Then
                'We add an empty dictionnary
                AddValue("<<")
                AddValue(">>")
            End If
        End If
    End Sub
    ''' <summary>
    ''' Add a byte to the object's content
    ''' </summary>
    ''' <param name="b"></param>
    ''' <remarks></remarks>
    Public Sub AddValue(ByVal b As Byte)
        If _value Is Nothing Then
            _count = 0
        Else
            _count += 1
        End If
        ReDim Preserve _value(_count)
        _value(_count) = b
    End Sub
    Public Sub SetValue(b As Byte())
        _count = b.Length
        _value = b
    End Sub
    ''' <summary>
    ''' Add a string to the object's content
    ''' </summary>
    ''' <param name="s"></param>
    ''' <remarks></remarks>
    Public Sub AddValue(ByVal s As String)
        Dim c As Char
        For Each c In s
            AddValue(Convert.ToByte(c))
        Next
    End Sub
    ''' <summary>
    ''' Add a number to the object's content
    ''' </summary>
    ''' <param name="number"></param>
    ''' <remarks></remarks>
    Public Sub AddValue(ByVal number As Long)
        AddValue(number.ToString)
    End Sub
    ''' <summary>
    ''' Add a number (double) to the object's content
    ''' </summary>
    ''' <param name="number"></param>
    ''' <remarks></remarks>
    Public Sub AddValue(ByVal number As Double)
        AddValue(number.ToString(CultureUS))
    End Sub
    ''' <summary>
    ''' Add a child object to the object
    ''' </summary>
    ''' <param name="obj"></param>
    ''' <remarks></remarks>
    Public Sub AddChild(ByVal obj As clsPdfBaseObject)
        _childs.Add(obj)
    End Sub
    ''' <summary>
    ''' Parse the object's content and build a list of PDF objects.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub ParseChildObjects()
        Dim obj As clsPdfBaseObject
        If Not _childsParsed Then
            'We create a base PDF reader connected to the binary content of the object
            Dim reader As New clsPDFBaseReader(_value)
            While Not reader.Eof
                obj = reader.GetObject
                If Not obj Is Nothing Then
                    AddChild(obj)
                End If
            End While
            _childsParsed = True
        End If
    End Sub
#End Region
#Region "Private methods"
    ''' <summary>
    ''' We get the string from a sequence of hexadecimal numbers
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function _hexadecimalStringValue() As String
        Dim i, n As Integer
        Dim s, hex As String
        _hexadecimalStringValue = ""
        s = _stringValue()
        Try
            i = 0
            While i < s.Length
                hex = s.Substring(i, 1)
                i += 1
                If i = s.Length Then
                    hex += "0"
                Else
                    hex += s.Substring(i, 1)
                End If
                n = Convert.ToInt32(hex, 16)
                _hexadecimalStringValue += Convert.ToChar(n)
                i += 1
            End While
        Catch ex As Exception
            _hexadecimalStringValue = s
        End Try
    End Function
    ''' <summary>
    ''' We have to escape some special characters 
    ''' </summary>
    ''' <param name="value"></param>
    ''' <remarks></remarks>
    Private Sub _literalStringValue(ByVal value As String)
        Dim c As Char
        ClearValue()
        For Each c In value
            If Asc(c) < 128 Then
                Select Case c
                    Case ControlChars.Lf
                        AddValue("\n")
                    Case ControlChars.Cr
                        AddValue("\r")
                    Case ControlChars.Tab
                        AddValue("\t")
                    Case ControlChars.Back
                        AddValue("\b")
                    Case ControlChars.FormFeed
                        AddValue("\f")
                    Case "("c
                        AddValue("\(")
                    Case ")"c
                        AddValue("\)")
                    Case "\"c
                        AddValue("\\")
                    Case Else
                        AddValue(c)
                End Select
            Else
                'Characters outside the 7 bits range are encoded in octal
                AddValue("\"c)
                AddValue(Oct(Asc(c)))
            End If
        Next
    End Sub
    ''' <summary>
    ''' Literal string contains escape characters, they are replaced with the proper char
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function _literalStringValue() As String
        Dim s, ns, octal As String
        Dim i, j, b As Integer
        s = _stringValue()

        ns = ""
        'We first do the conversion of octal values
        For i = 0 To s.Length - 1
            If s(i) = "\"c Then
                octal = ""
                For j = (i + 1) To s.Length - 1
                    If s(j) = "\"c And j = (i + 1) Then
                        ns += "\\"
                        i += 1
                        Exit For
                    End If
                    If Not Char.IsNumber(s(j)) Then
                        If octal.Length > 0 And octal.Length <= 3 Then
                            'We have an octal value
                            Try
                                b = Convert.ToInt32(octal, 8)
                                ns += Convert.ToChar(b)
                            Catch ex As Exception

                            End Try
                            i = j - 1
                            Exit For
                        Else
                            'This is not an octal value
                            ns += s(i)
                            Exit For
                        End If
                    Else
                        octal += s(j)
                    End If
                Next
            Else
                ns += s(i)
            End If
        Next

        s = ns

        'Next we replace escaped characters
        s = s.Replace("\n", ControlChars.Lf)
        s = s.Replace("\r", ControlChars.Cr)
        s = s.Replace("\t", ControlChars.Tab)
        s = s.Replace("\b", ControlChars.Back)
        s = s.Replace("\f", ControlChars.FormFeed)
        s = s.Replace("\(", "(")
        s = s.Replace("\)", ")")
        s = s.Replace("\\", "\")
        _literalStringValue = s
    End Function
    Private Function _stringValue() As String
        Dim b As Byte
        _stringValue = ""
        If Not _value Is Nothing Then
            For Each b In _value
                _stringValue += Convert.ToChar(b)
            Next
        End If
    End Function
    Private Function _longValue() As Integer
        Dim v As String
        v = _stringValue()
        _longValue = Convert.ToInt32(v)
    End Function
    Private Function _doubleValue() As Double
        Dim v As String
        v = _stringValue()
        _doubleValue = Double.Parse(v, CultureUS)
    End Function
#End Region
End Class
